# visualizacje
import matplotlib.pyplot as plt
import seaborn as sns
# data manipulation
import numpy as np
import pandas as pd
import phik
from scipy import stats
import math
from pandas_profiling import ProfileReport
import statsmodels.stats.weightstats as st
# printing
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
from IPython.display import display, HTML
from IPython.display import Image
%matplotlib inline
df = pd.read_csv("BankChurners.csv")
display(df.head())
| CLIENTNUM | Attrition_Flag | Customer_Age | Gender | Dependent_count | Education_Level | Marital_Status | Income_Category | Card_Category | Months_on_book | ... | Credit_Limit | Total_Revolving_Bal | Avg_Open_To_Buy | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1 | Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 768805383 | Existing Customer | 45 | M | 3 | High School | Married | $60K - $80K | Blue | 39 | ... | 12691.0 | 777 | 11914.0 | 1.335 | 1144 | 42 | 1.625 | 0.061 | 0.000093 | 0.99991 |
| 1 | 818770008 | Existing Customer | 49 | F | 5 | Graduate | Single | Less than $40K | Blue | 44 | ... | 8256.0 | 864 | 7392.0 | 1.541 | 1291 | 33 | 3.714 | 0.105 | 0.000057 | 0.99994 |
| 2 | 713982108 | Existing Customer | 51 | M | 3 | Graduate | Married | $80K - $120K | Blue | 36 | ... | 3418.0 | 0 | 3418.0 | 2.594 | 1887 | 20 | 2.333 | 0.000 | 0.000021 | 0.99998 |
| 3 | 769911858 | Existing Customer | 40 | F | 4 | High School | Unknown | Less than $40K | Blue | 34 | ... | 3313.0 | 2517 | 796.0 | 1.405 | 1171 | 20 | 2.333 | 0.760 | 0.000134 | 0.99987 |
| 4 | 709106358 | Existing Customer | 40 | M | 3 | Uneducated | Married | $60K - $80K | Blue | 21 | ... | 4716.0 | 0 | 4716.0 | 2.175 | 816 | 28 | 2.500 | 0.000 | 0.000022 | 0.99998 |
5 rows × 23 columns
Ponieważ nie znamy parametrów tych modeli poza tym jakie zmienne zostały w nich wykorzystane nie możemy użyć tych zmiennych, jak i nie ma ich w specyfikacji samego zadania, dlatego pozbywamy się tych zmiennych. Wyrzucamy równiez zmienną CLIENTNUM, która stanowi tylko index dla naszych danych.
df = df.drop(["Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2", "Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1","CLIENTNUM"],
axis = 1)
Mamy parę niezgodnych typów kategorycznych rozpoznanych jako typ "string". Poprawiamy to.
df = df.convert_dtypes()
display(df.info())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10127 entries, 0 to 10126 Data columns (total 20 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Attrition_Flag 10127 non-null string 1 Customer_Age 10127 non-null Int64 2 Gender 10127 non-null string 3 Dependent_count 10127 non-null Int64 4 Education_Level 10127 non-null string 5 Marital_Status 10127 non-null string 6 Income_Category 10127 non-null string 7 Card_Category 10127 non-null string 8 Months_on_book 10127 non-null Int64 9 Total_Relationship_Count 10127 non-null Int64 10 Months_Inactive_12_mon 10127 non-null Int64 11 Contacts_Count_12_mon 10127 non-null Int64 12 Credit_Limit 10127 non-null float64 13 Total_Revolving_Bal 10127 non-null Int64 14 Avg_Open_To_Buy 10127 non-null float64 15 Total_Amt_Chng_Q4_Q1 10127 non-null float64 16 Total_Trans_Amt 10127 non-null Int64 17 Total_Trans_Ct 10127 non-null Int64 18 Total_Ct_Chng_Q4_Q1 10127 non-null float64 19 Avg_Utilization_Ratio 10127 non-null float64 dtypes: Int64(9), float64(5), string(6) memory usage: 1.6 MB
None
Sprawdzamy czy są duplikaty oraz puste wartości.
display(pd.DataFrame([df.duplicated().sum()],columns=["Liczba duplikatów"]).style.hide_index())
| Liczba duplikatów |
|---|
| 0 |
Dane są czyste i możemy z nimi pracować.
profile = ProfileReport(df, config_file="config.yml")
profile.to_notebook_iframe()
Poniższa kolumna przedstawia korelacje typu phik, im warość jest bliższa 1, tym bardziej skorelowane ze sobą są dwie wartości.
# popraw na zmienne kategoryczne
for c in ["Attrition_Flag","Gender","Education_Level","Marital_Status","Income_Category","Card_Category"]:
df[c] = df[c].replace("Unknown",np.nan,method="pad")
df[c] = df[c].astype("category")
# Ustawienie zmiennych na typu float
for c in df.columns:
if df[c].dtype.__str__() == "Int64":
df[c] = df[c].astype("float64")
display(df.isna().sum().to_frame("Ilość NA"))
| Ilość NA | |
|---|---|
| Attrition_Flag | 0 |
| Customer_Age | 0 |
| Gender | 0 |
| Dependent_count | 0 |
| Education_Level | 1519 |
| Marital_Status | 749 |
| Income_Category | 1112 |
| Card_Category | 0 |
| Months_on_book | 0 |
| Total_Relationship_Count | 0 |
| Months_Inactive_12_mon | 0 |
| Contacts_Count_12_mon | 0 |
| Credit_Limit | 0 |
| Total_Revolving_Bal | 0 |
| Avg_Open_To_Buy | 0 |
| Total_Amt_Chng_Q4_Q1 | 0 |
| Total_Trans_Amt | 0 |
| Total_Trans_Ct | 0 |
| Total_Ct_Chng_Q4_Q1 | 0 |
| Avg_Utilization_Ratio | 0 |
corr = df.phik_matrix()
display(corr["Attrition_Flag"].sort_values(ascending = False).to_frame())
interval columns not set, guessing: ['Customer_Age', 'Dependent_count', 'Months_on_book', 'Total_Relationship_Count', 'Months_Inactive_12_mon', 'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Revolving_Bal', 'Avg_Open_To_Buy', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt', 'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio']
| Attrition_Flag | |
|---|---|
| Attrition_Flag | 1.000000 |
| Total_Trans_Ct | 0.592470 |
| Total_Revolving_Bal | 0.522422 |
| Total_Trans_Amt | 0.423372 |
| Total_Ct_Chng_Q4_Q1 | 0.409053 |
| Avg_Utilization_Ratio | 0.314541 |
| Total_Relationship_Count | 0.230833 |
| Contacts_Count_12_mon | 0.223812 |
| Total_Amt_Chng_Q4_Q1 | 0.184180 |
| Months_Inactive_12_mon | 0.183537 |
| Gender | 0.055962 |
| Education_Level | 0.039007 |
| Customer_Age | 0.031183 |
| Dependent_count | 0.029239 |
| Credit_Limit | 0.028030 |
| Months_on_book | 0.025403 |
| Avg_Open_To_Buy | 0.025286 |
| Income_Category | 0.024901 |
| Marital_Status | 0.011258 |
| Card_Category | 0.000000 |
#Hipoteza 1
df.Card_Category.value_counts()
df_h1 = df.Card_Category == "Blue"
df_h1_prem = df.Card_Category == "Platinum"
Blue 9436 Silver 555 Gold 116 Platinum 20 Name: Card_Category, dtype: int64
df["Attrition_Flag_onehot"] = pd.get_dummies(df["Attrition_Flag"]).iloc[:,0]
df[["Attrition_Flag","Attrition_Flag_onehot"]].head()
| Attrition_Flag | Attrition_Flag_onehot | |
|---|---|---|
| 0 | Existing Customer | 0 |
| 1 | Existing Customer | 0 |
| 2 | Existing Customer | 0 |
| 3 | Existing Customer | 0 |
| 4 | Existing Customer | 0 |
prob_blue = df[df_h1]["Attrition_Flag"].value_counts()
d_blue = {
"Prawdobieństwo odejścia":prob_blue.iloc[1] / prob_blue.iloc[0],
"Liczebność populacji":prob_blue.iloc[1] + prob_blue.iloc[0]
}
display(pd.DataFrame.from_dict(d_blue,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.191866 |
| Liczebność populacji | 9436.000000 |
prob_rest = df[~df_h1]["Attrition_Flag"].value_counts()
d_rest = {
"Prawdobieństwo odejścia":prob_rest.iloc[1] / prob_rest.iloc[0],
"Liczebność populacji":prob_rest.iloc[1] + prob_rest.iloc[0]
}
display(pd.DataFrame.from_dict(d_rest,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.185249 |
| Liczebność populacji | 691.000000 |
display(pd.DataFrame.from_dict({
"P-value dla t testu, porównanie między 'Blue' a 'Platinum' klasą karty":
st.ttest_ind(df[df_h1_prem]["Attrition_Flag_onehot"],df[df_h1]["Attrition_Flag_onehot"],
alternative="larger")[1]
}, orient='index'))
| 0 | |
|---|---|
| P-value dla t testu, porównanie między 'Blue' a 'Platinum' klasą karty | 0.139738 |
display(pd.DataFrame.from_dict({
"P-value dla t testu, porównanie między 'Blue' a pozostałymi klasami kart":
st.ttest_ind(df[~df_h1]["Attrition_Flag_onehot"],df[df_h1]["Attrition_Flag_onehot"], alternative="larger")[1]
}, orient='index'))
| 0 | |
|---|---|
| P-value dla t testu, porównanie między 'Blue' a pozostałymi klasami kart | 0.626887 |
Zakładamy, że osoby wykonujące dużo transakcji znajdują się w 90 percentylu wykonanych transakcji(Wartość percentyla należało by wyestymować budując modela, ale to odbędzie się w dalszych krokach analizy).
df["Avg_Trans_Amt"] = df.Total_Trans_Amt/df.Total_Trans_Ct
corr = df[["Attrition_Flag_onehot","Total_Trans_Ct","Avg_Trans_Amt","Total_Trans_Amt"]].corr(method='kendall')
display(corr["Attrition_Flag_onehot"].to_frame())
| Attrition_Flag_onehot | |
|---|---|
| Attrition_Flag_onehot | 1.000000 |
| Total_Trans_Ct | -0.309061 |
| Avg_Trans_Amt | -0.005430 |
| Total_Trans_Amt | -0.182744 |
Zmienna ta koreluje o wiele gorzej niż oddzielnie liczba transakcji oraz wartość transakcji. Wybieramy do dalszej analizy liczby przeprowadzonych transakcji oraz porzucamy stworzoną zmienną.
q_90 = df.Total_Trans_Ct.quantile(0.9)
display(pd.DataFrame([q_90],columns=["Wartość 90 percentyla"]).style.hide_index())
| Wartość 90 percentyla |
|---|
| 92.000000 |
df_h2 = df.Total_Trans_Ct >= q_90
prob_above_q90 = df[df_h2]["Attrition_Flag"].value_counts()
d_above = {
"Prawdobieństwo odejścia":prob_above_q90.iloc[1] / prob_above_q90.iloc[0],
"Liczebność populacji":prob_above_q90.iloc[1] + prob_above_q90.iloc[0]
}
display(pd.DataFrame.from_dict(d_above,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.00095 |
| Liczebność populacji | 1054.00000 |
prob_bellow_q90 = df[~df_h2]["Attrition_Flag"].value_counts()
d_bellow = {
"Prawdobieństwo odejścia":prob_bellow_q90.iloc[1] / prob_bellow_q90.iloc[0],
"Liczebność populacji":prob_bellow_q90.iloc[1] + prob_bellow_q90.iloc[0]
}
display(pd.DataFrame.from_dict(d_bellow,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.218343 |
| Liczebność populacji | 9073.000000 |
display(pd.DataFrame.from_dict({
"P-value dla t testu, porównanie miedzy większymi od 90 percentyla, a resztą":
st.ttest_ind(df[~df_h2]["Attrition_Flag_onehot"],df[df_h2]["Attrition_Flag_onehot"],
alternative="larger")[1]
}, orient='index'))
| 0 | |
|---|---|
| P-value dla t testu, porównanie miedzy większymi od 90 percentyla, a resztą | 3.747446e-51 |
q_10 = df.Total_Trans_Ct.quantile(0.1)
display(pd.DataFrame([q_10],columns=["Wartość 10 percentyla"]).style.hide_index())
| Wartość 10 percentyla |
|---|
| 33.000000 |
df_h2_1 = df.Total_Trans_Ct <= q_10
prob_bellow_q10 = df[df_h2_1]["Attrition_Flag"].value_counts()
d_above = {
"Prawdobieństwo odejścia":prob_bellow_q10.iloc[1] / prob_bellow_q10.iloc[0],
"Liczebność populacji":prob_bellow_q10.iloc[1] + prob_bellow_q10.iloc[0]
}
display(pd.DataFrame.from_dict(d_above,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.362784 |
| Liczebność populacji | 1018.000000 |
prob_above_q10 = df[~df_h2_1]["Attrition_Flag"].value_counts()
d_bellow = {
"Prawdobieństwo odejścia":prob_above_q10.iloc[1] / prob_above_q10.iloc[0],
"Liczebność populacji":prob_above_q10.iloc[1] + prob_above_q10.iloc[0]
}
display(pd.DataFrame.from_dict(d_bellow,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.1749 |
| Liczebność populacji | 9109.0000 |
display(pd.DataFrame.from_dict({
"P-value dla t testu, porównanie miedzy większymi od 10 percentyla, a resztą":
st.ttest_ind(df[~df_h2_1]["Attrition_Flag_onehot"],df[df_h2_1]["Attrition_Flag_onehot"],
alternative="smaller")[1]
}, orient='index'))
| 0 | |
|---|---|
| P-value dla t testu, porównanie miedzy większymi od 10 percentyla, a resztą | 1.648878e-22 |
Im więcej transakcji wykonuje dana osoba tym mniejsza jest jej szansa na odejście.
Robimy takie same założenia jak testując wcześniejsze hipotezy.
df.Months_on_book.describe()
count 10127.000000 mean 35.928409 std 7.986416 min 13.000000 25% 31.000000 50% 36.000000 75% 40.000000 max 56.000000 Name: Months_on_book, dtype: float64
q_90 = df.Months_on_book.quantile(0.9)
display(pd.DataFrame([q_90],columns=["Wartość 90 percentyla"]).style.hide_index())
df_h3 = df.Months_on_book >= q_90
| Wartość 90 percentyla |
|---|
| 46.000000 |
prob_above_q90 = df[df_h3]["Attrition_Flag"].value_counts()
d_above = {
"Prawdobieństwo odejścia":prob_above_q90.iloc[1] / prob_above_q90.iloc[0],
"Liczebność populacji":prob_above_q90.iloc[1] + prob_above_q90.iloc[0]
}
display(pd.DataFrame.from_dict(d_above,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.200608 |
| Liczebność populacji | 1185.000000 |
prob_bellow_q90 = df[~df_h3]["Attrition_Flag"].value_counts()
d_above = {
"Prawdobieństwo odejścia":prob_bellow_q90.iloc[1] / prob_bellow_q90.iloc[0],
"Liczebność populacji":prob_bellow_q90.iloc[1] + prob_bellow_q90.iloc[0]
}
display(pd.DataFrame.from_dict(d_above,orient='index'))
| 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.190204 |
| Liczebność populacji | 8942.000000 |
display(pd.DataFrame.from_dict({
"P-value dla t testu, porównanie miedzy większymi od 90 percentyla, a resztą":
st.ttest_ind(df[~df_h3]["Attrition_Flag_onehot"],df[df_h3]["Attrition_Flag_onehot"],
alternative="larger")[1]
}, orient='index'))
| 0 | |
|---|---|
| P-value dla t testu, porównanie miedzy większymi od 90 percentyla, a resztą | 0.739332 |
Przed zakończeniem analizy zbadajmy jeszcze wizualnie jak zachowują się nasze zmienne, patrząc na róznice wizualne w parach wykresów wielu zmiennych.
df.columns
Index(['Attrition_Flag', 'Customer_Age', 'Gender', 'Dependent_count',
'Education_Level', 'Marital_Status', 'Income_Category', 'Card_Category',
'Months_on_book', 'Total_Relationship_Count', 'Months_Inactive_12_mon',
'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Revolving_Bal',
'Avg_Open_To_Buy', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt',
'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio',
'Attrition_Flag_onehot', 'Avg_Trans_Amt'],
dtype='object')
sns.pairplot(df[["Attrition_Flag",
"Credit_Limit",
"Total_Revolving_Bal",
"Total_Amt_Chng_Q4_Q1",
"Total_Trans_Amt",
"Total_Trans_Ct",
"Total_Ct_Chng_Q4_Q1",
"Avg_Utilization_Ratio",
"Avg_Open_To_Buy",
"Months_on_book",
'Total_Relationship_Count',
'Months_Inactive_12_mon',
'Contacts_Count_12_mon',
]],
hue = "Attrition_Flag"
)
<seaborn.axisgrid.PairGrid at 0x227458d4b80>
df.Contacts_Count_12_mon.value_counts()
3.0 3380 2.0 3227 1.0 1499 4.0 1392 0.0 399 5.0 176 6.0 54 Name: Contacts_Count_12_mon, dtype: int64
# anliza odejścia klienta po dużej liczbie kontaktów
for i in range(0,7):
df_contact = df.Contacts_Count_12_mon == i
prob = df[df_contact]["Attrition_Flag"].value_counts()
d_above = {
"Prawdobieństwo odejścia":prob.iloc[1] / prob.iloc[0],
"Liczebnosc populacji":prob.iloc[1] + prob.iloc[0]
}
display(pd.DataFrame.from_dict(d_above,orient='index', columns=[f"liczba kontaktów {i}"]))
| liczba kontaktów 0 | |
|---|---|
| Prawdobieństwo odejścia | 0.017857 |
| Liczebnosc populacji | 399.000000 |
| liczba kontaktów 1 | |
|---|---|
| Prawdobieństwo odejścia | 0.077642 |
| Liczebnosc populacji | 1499.000000 |
| liczba kontaktów 2 | |
|---|---|
| Prawdobieństwo odejścia | 0.142705 |
| Liczebnosc populacji | 3227.000000 |
| liczba kontaktów 3 | |
|---|---|
| Prawdobieństwo odejścia | 0.252316 |
| Liczebnosc populacji | 3380.000000 |
| liczba kontaktów 4 | |
|---|---|
| Prawdobieństwo odejścia | 0.292479 |
| Liczebnosc populacji | 1392.000000 |
| liczba kontaktów 5 | |
|---|---|
| Prawdobieństwo odejścia | 0.504274 |
| Liczebnosc populacji | 176.000000 |
| liczba kontaktów 6 | |
|---|---|
| Prawdobieństwo odejścia | 0.0 |
| Liczebnosc populacji | 54.0 |
Dla bardzo wysokiej liczby kontaktów z klientem oraz bardzo niskiej jego szansa na odejście jest niska.
# niezbalansowane dane
from imblearn.over_sampling import SMOTE
#metryki
from sklearn.metrics import confusion_matrix, f1_score, accuracy_score
from sklearn import model_selection
# podiział an test-train
from sklearn.model_selection import train_test_split
# modele
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_graphviz
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
selected_columns = [
"Total_Revolving_Bal",
"Total_Amt_Chng_Q4_Q1",
"Total_Trans_Amt",
"Total_Trans_Ct",
"Total_Ct_Chng_Q4_Q1",
"Avg_Utilization_Ratio",
'Total_Relationship_Count',
'Contacts_Count_12_mon',
]
# oversampling do zbalansowania danych
sm = SMOTE()
X, y = sm.fit_resample(df[selected_columns], df["Attrition_Flag_onehot"])
X_train, X_test, y_train, y_test = train_test_split(X, y , test_size=0.2, random_state=42)
print(f"Balans zmiennych PRZED oversamplingu {(df['Attrition_Flag_onehot'] == 1).sum()/(df['Attrition_Flag_onehot'] == 0).sum()} ")
print(f"Balans zmiennych PO oversamplingu {(y == 0).sum()/(y == 1).sum()} ")
Balans zmiennych PRZED oversamplingu 0.19141176470588236 Balans zmiennych PO oversamplingu 1.0
clf_max3 = DecisionTreeClassifier(max_depth = 3)
clf_max3.fit(X_train, y_train)
DecisionTreeClassifier(max_depth=3)
export_graphviz(clf_max3, out_file="tree.dot", class_names=["Otwarte konto", "Zamknięte konto"],feature_names= X.columns, impurity=False, filled=True)
!dot -Tpng tree.dot -o tree_limited.png -Gdpi=1200
Image(filename = "tree_limited.png")
Interpretacja drzewa polaga na przejściu przez drzewo sprawdzając podane warunki. Im mocniejszy kolor, tym większą pewność mamy o poprawnym przewidzeniu.
pred_dtc = clf_max3.predict(X_test)
conf_dec_tree = confusion_matrix(y_test,pred_dtc )
display(pd.DataFrame(conf_dec_tree,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_desion_max3_balanced = pd.DataFrame(
[accuracy_score(y_test,pred_dtc),
f1_score(y_test,pred_dtc)
],index= ["Accuracy", "f1_score"], columns=["Drzewo decyzjne o głebokości 3"]
)
display(score_desion_max3_balanced)
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1595 | 81 |
| Acual False | 400 | 1324 |
| Drzewo decyzjne o głebokości 3 | |
|---|---|
| Accuracy | 0.858529 |
| f1_score | 0.846277 |
clf_balanced = DecisionTreeClassifier()
clf_balanced.fit(X_train, y_train)
pred_dtc = clf_balanced.predict(X_test)
conf_dec_tree_balanced = confusion_matrix(y_test,pred_dtc )
display(pd.DataFrame(conf_dec_tree_balanced,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_decison_balanced = pd.DataFrame(
[accuracy_score(y_test,pred_dtc),
f1_score(y_test,pred_dtc)
],index= ["Accuracy", "f1_score"], columns=[f"Drzewo decyzjne o głebokości {clf_balanced.get_depth()} "]
)
display(score_decison_balanced)
DecisionTreeClassifier()
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1598 | 78 |
| Acual False | 66 | 1658 |
| Drzewo decyzjne o głebokości 22 | |
|---|---|
| Accuracy | 0.957647 |
| f1_score | 0.958382 |
X_train_u, X_test_u, y_train_u, y_test_u = train_test_split(df[selected_columns], df["Attrition_Flag_onehot"] , test_size=0.2, random_state=42)
clf_unbalanced = DecisionTreeClassifier()
clf_unbalanced.fit(X_train_u, y_train_u)
pred_dtc_u = clf_unbalanced.predict(X_test_u)
conf_dec_tree = confusion_matrix(y_test_u,pred_dtc_u )
display(pd.DataFrame(conf_dec_tree,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_deison_unbalanced = pd.DataFrame(
[accuracy_score(y_test_u,pred_dtc_u),
f1_score(y_test_u,pred_dtc_u)
],index= ["Accuracy", "f1_score"], columns=[f"Drzewo decyzjne o głebokości {clf_unbalanced.get_depth()} "]
)
display(score_deison_unbalanced)
DecisionTreeClassifier()
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1624 | 75 |
| Acual False | 62 | 265 |
| Drzewo decyzjne o głebokości 19 | |
|---|---|
| Accuracy | 0.932379 |
| f1_score | 0.794603 |
logit_model = LogisticRegression().fit(X_train,y_train)
logit_pred = logit_model.predict(X_test)
c:\python38\lib\site-packages\sklearn\linear_model\_logistic.py:763: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
n_iter_i = _check_optimize_result(
conf_logit = confusion_matrix(y_test,logit_pred )
display(pd.DataFrame(conf_logit,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_logit= pd.DataFrame(
[accuracy_score(y_test,logit_pred),
f1_score(y_test,logit_pred)
],index= ["Accuracy", "f1_score"], columns=[f"Regresja logistyczna"]
)
display(score_logit)
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1375 | 301 |
| Acual False | 269 | 1455 |
| Regresja logistyczna | |
|---|---|
| Accuracy | 0.832353 |
| f1_score | 0.836207 |
logit_model.intercept_
array([5.5755538])
print("Wyniki te są porównywane do przypadku gdy wartość pozostałych wartośći jest równa średniej wartości dla danej zmiennej, ")
for i, c in enumerate(X_train.columns):
prob = logit_model.coef_[0][i]
print(f"Parametr {c} o wartośći równej {prob:.4f}.")
Wyniki te są porównywane do przypadku gdy wartość pozostałych wartośći jest równa średniej wartości dla danej zmiennej, Parametr Total_Revolving_Bal o wartośći równej -0.0009. Parametr Total_Amt_Chng_Q4_Q1 o wartośći równej 1.6763. Parametr Total_Trans_Amt o wartośći równej 0.0005. Parametr Total_Trans_Ct o wartośći równej -0.1251. Parametr Total_Ct_Chng_Q4_Q1 o wartośći równej -1.2723. Parametr Avg_Utilization_Ratio o wartośći równej 0.3286. Parametr Total_Relationship_Count o wartośći równej -0.4530. Parametr Contacts_Count_12_mon o wartośći równej 0.5873.
Regresja logistyczna dała nam zadowalające wyniki, przy czym każdy jej parametr jest interpretowalny. Same wynki modelu są zadowalające.
clf_bayes = GaussianNB()
clf_bayes.fit(X_train ,y_train)
pred_bayes = clf_bayes.predict(X_test)
conf_dec_tree = confusion_matrix(y_test,pred_bayes)
display(pd.DataFrame(conf_dec_tree,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_bayes = pd.DataFrame(
[accuracy_score(y_test,pred_bayes),
f1_score(y_test,pred_bayes)
],index= ["Accuracy", "f1_score"], columns=[f"Naive bayes zbalansowane dane"]
)
display(score_bayes)
GaussianNB()
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1283 | 393 |
| Acual False | 337 | 1387 |
| Naive bayes zbalansowane dane | |
|---|---|
| Accuracy | 0.785294 |
| f1_score | 0.791667 |
clf_bayes_unbalanced = GaussianNB()
clf_bayes_unbalanced.fit(X_train_u ,y_train_u)
pred_bayes_unbalaned = clf_bayes_unbalanced.predict(X_test_u)
conf_dec_tree = confusion_matrix(y_test_u,pred_bayes_unbalaned)
display(pd.DataFrame(conf_dec_tree,
index= ["Acucal True", "Acual False"],
columns= ["Predicted True", "Prediced False"]) )
score_bayes = pd.DataFrame(
[accuracy_score(y_test_u,pred_bayes_unbalaned),
f1_score(y_test_u,pred_bayes_unbalaned)
],index= ["Accuracy", "f1_score"], columns=[f"Naive bayes nie zbalansowane"]
)
display(score_bayes)
GaussianNB()
| Predicted True | Prediced False | |
|---|---|---|
| Acucal True | 1574 | 125 |
| Acual False | 131 | 196 |
| Naive bayes nie zbalansowane | |
|---|---|
| Accuracy | 0.873643 |
| f1_score | 0.604938 |
Widzmy overfit na niezbalansowaną kategorię w przypadku, gdy nie balansujemy naszych danych. Dlatego do interptetacji wyników użyjemy modelu trenowanego na zbalansowanych danych.
Do porównania wykorzysty kross validacje wraz z dodaniem dodatkowych modeli, które nie zostały opisane w wcześniejszej analizie.
%%capture
models = []
models.append(('Logistic regresion', LogisticRegression()))
models.append(('Decision tree', DecisionTreeClassifier()))
models.append(('Decision tree max depth 3', DecisionTreeClassifier(max_depth=3)))
models.append(('Naive Bayes', GaussianNB()))
# evaluate each model in turn
results = []
for name, model in models:
kfold = model_selection.KFold(n_splits=10)
cv_results = model_selection.cross_val_score(model, X, y, cv=kfold, scoring="accuracy")
cv_results_f1 = model_selection.cross_val_score(model, X, y, cv=kfold, scoring="f1")
msg = f"{name} accuarcy = {cv_results.mean()}, F1_score = {cv_results_f1.mean()}"
results.append(msg)
results
['Logistic regresion accuarcy = 0.7228823529411764, F1_score = 0.6712847051032338', 'Decision tree accuarcy = 0.865, F1_score = 0.7835826783709179', 'Decision tree max depth 3 accuarcy = 0.697764705882353, F1_score = 0.6400653452818551', 'Naive Bayes accuarcy = 0.6981764705882354, F1_score = 0.6580180006644217']
df_full = pd.get_dummies(df.drop(["Attrition_Flag_onehot","Attrition_Flag"],axis =1),
drop_first=True)
df_full.columns
Index(['Customer_Age', 'Dependent_count', 'Months_on_book',
'Total_Relationship_Count', 'Months_Inactive_12_mon',
'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Revolving_Bal',
'Avg_Open_To_Buy', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt',
'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio',
'Avg_Trans_Amt', 'Gender_M', 'Education_Level_Doctorate',
'Education_Level_Graduate', 'Education_Level_High School',
'Education_Level_Post-Graduate', 'Education_Level_Uneducated',
'Marital_Status_Married', 'Marital_Status_Single',
'Income_Category_$40K - $60K', 'Income_Category_$60K - $80K',
'Income_Category_$80K - $120K', 'Income_Category_Less than $40K',
'Card_Category_Gold', 'Card_Category_Platinum', 'Card_Category_Silver'],
dtype='object')
%%capture
X, y = sm.fit_resample(df_full, df["Attrition_Flag_onehot"])
models = []
models.append(('Logistic regresion', LogisticRegression()))
models.append(('Decision tree', DecisionTreeClassifier()))
models.append(('Decision tree max depth 3', DecisionTreeClassifier(max_depth=3)))
models.append(('Naive Bayes', GaussianNB()))
# evaluate each model in turn
results = []
for name, model in models:
kfold = model_selection.KFold(n_splits=10)
cv_results = model_selection.cross_val_score(model, X, y, cv=kfold, scoring="accuracy")
cv_results_f1 = model_selection.cross_val_score(model, X, y, cv=kfold, scoring="f1")
msg = f"{name} accuarcy = {cv_results.mean()}, F1_score = {cv_results_f1.mean()}"
results.append(msg)
results
['Logistic regresion accuarcy = 0.806470588235294, F1_score = 0.7184224563350561', 'Decision tree accuarcy = 0.8748823529411764, F1_score = 0.7849617864478905', 'Decision tree max depth 3 accuarcy = 0.7377647058823529, F1_score = 0.6761603951122961', 'Naive Bayes accuarcy = 0.7370000000000001, F1_score = 0.6654344867743418']